On a Matplotlib log scale the minor ticks between the powers of ten often vanish. The fix is a LogLocator with a subs argument:
from matplotlib.ticker import LogLocator
ax.xaxis.set_minor_locator(LogLocator(base=10.0, subs=np.arange(2, 10) * 0.1, numticks=100))
That subs list is what places a tick at 2, 3, 4 and so on inside every decade. Without it you get one tick per power of ten and nothing between.
The plots and terminal output below came off Matplotlib 3.11.2 on Python 3.12.5 on my machine.
Why do log scale minor ticks disappear?
A log axis uses LogLocator by default, and its job is to avoid clutter. Across many decades it drops the in-between ticks entirely:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
x = np.logspace(0, 4, 200)
# a narrow decade range: minorticks_on() is enough
fig, ax = plt.subplots()
ax.plot(x[:80], np.sqrt(x[:80]))
ax.set_xscale("log")
ax.minorticks_on()
print("narrow range, minorticks_on ->", len(ax.xaxis.get_minorticklocs()), "minor ticks")
plt.close(fig)
# over many decades Matplotlib thins them out on its own
fig, ax = plt.subplots()
ax.plot(np.logspace(0, 12, 300), np.logspace(0, 6, 300))
ax.set_xscale("log")
ax.minorticks_on()
print("12 decades, minorticks_on ->", len(ax.xaxis.get_minorticklocs()), "minor ticks")
plt.close(fig)
Output:
narrow range, minorticks_on -> 24 minor ticks
12 decades, minorticks_on -> 0 minor ticks
Over a couple of decades ax.minorticks_on() is all you need. Stretch the axis over twelve and Matplotlib thins them out, which is why the same call looks like it’s stopped working.
That’s the moment to set the locator yourself rather than asking for minor ticks in general. You’re not fighting a bug, you’re overriding a decluttering rule.
Setting minor ticks with LogLocator and subs
subs takes the positions inside each decade, expressed as fractions. np.arange(2, 10) * 0.1 gives 0.2 through 0.9, which lands ticks on 2, 3, 4 … 9 of every power:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import LogLocator
x = np.logspace(0, 4, 200)
fig, ax = plt.subplots(figsize=(6, 3.5))
ax.plot(x, np.sqrt(x))
ax.set_xscale("log")
ax.set_yscale("log")
# this is the line people come looking for
ax.xaxis.set_minor_locator(LogLocator(base=10.0, subs=np.arange(2, 10) * 0.1, numticks=100))
print("major x ticks:", len(ax.xaxis.get_majorticklocs()))
print("minor x ticks:", len(ax.xaxis.get_minorticklocs()))
plt.close(fig)
Output:
major x ticks: 7
minor x ticks: 48
subs filling in 2 through 9.
numticks is the argument people leave out. It caps how many ticks the locator will return, and the default is low enough to suppress everything you just asked for.
Set it generously, such as 100. The locator still won’t draw ticks outside the axis limits, so there’s no cost to a large number.
loglog, semilogx and semilogy
Three helpers set the scales for you, and set_xscale does the same job when you already have an axes:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
x = np.logspace(0, 3, 50)
y = x ** 1.8
fig, axes = plt.subplots(1, 3, figsize=(10, 3))
axes[0].loglog(x, y) # both axes logarithmic
axes[1].semilogx(x, y) # x only
axes[2].semilogy(x, y) # y only
for ax, name in zip(axes, ("loglog", "semilogx", "semilogy")):
print(f"{name:<9} x={ax.get_xscale():<6} y={ax.get_yscale()}")
plt.close(fig)
# the same thing without the helper functions
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_xscale("log"); ax.set_yscale("log")
print("set_xscale ->", ax.get_xscale(), ax.get_yscale())
plt.close(fig)
Output:
loglog x=log y=log
semilogx x=log y=linear
semilogy x=linear y=log
set_xscale -> log log
set_xscale reaches the same place.| Call | x axis | y axis |
|---|---|---|
ax.loglog(x, y) | log | log |
ax.semilogx(x, y) | log | linear |
ax.semilogy(x, y) | linear | log |
ax.set_xscale("log") | log | unchanged |
The helpers plot and set the scale in one call. set_xscale is better when the plotting already happened, or when you’re adjusting ticks and labels afterwards.
Labelling the minor ticks
Minor ticks are drawn unlabelled by default. A LogFormatter with labelOnlyBase=False puts numbers on them:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import LogLocator, LogFormatter, ScalarFormatter
x = np.logspace(0, 3, 100)
fig, ax = plt.subplots(figsize=(6, 3.5))
ax.plot(x, x ** 0.5)
ax.set_xscale("log")
ax.xaxis.set_minor_locator(LogLocator(base=10.0, subs=np.arange(2, 10) * 0.1, numticks=100))
ax.xaxis.set_minor_formatter(LogFormatter(labelOnlyBase=False, minor_thresholds=(3, 0.5)))
labelled = [t for t in ax.xaxis.get_minorticklabels() if t.get_text()]
print("minor ticks:", len(ax.xaxis.get_minorticklocs()))
# plain numbers instead of powers of ten on the major ticks
ax.xaxis.set_major_formatter(ScalarFormatter())
fig.canvas.draw()
print("major labels:", [t.get_text() for t in ax.xaxis.get_majorticklabels()][:6])
plt.close(fig)
Output:
minor ticks: 40
major labels: ['0', '1', '10', '100', '1000', '10000']
ScalarFormatter on the major axis is the other half of this. It swaps 10^3 for a plain 1000, which reads better on small ranges.
Label minor ticks sparingly. On a busy axis they collide, and the marks alone usually give enough sense of scale, so I’d leave them unlabelled unless a reader has to read exact values off the chart.
Log colorbars with LogNorm
A log colour scale is a different setting: it belongs to the norm, not the axes. LogNorm maps the data logarithmically and the colorbar follows:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import LogNorm
rng = np.random.default_rng(0)
data = rng.lognormal(mean=1.0, sigma=2.0, size=(40, 60))
fig, ax = plt.subplots(figsize=(6, 3))
mesh = ax.pcolormesh(data, norm=LogNorm(vmin=data.min(), vmax=data.max()), cmap="viridis")
bar = fig.colorbar(mesh, ax=ax)
print("data range :", f"{data.min():.4f}", "to", f"{data.max():.1f}")
print("norm :", type(mesh.norm).__name__)
print("colorbar scale:", bar.ax.get_yscale())
plt.close(fig)
Output:
data range : 0.0011 to 1251.5
norm : LogNorm
colorbar scale: log
LogNorm recovers the structure.It’s worth reaching for whenever your values span orders of magnitude. A linear colour map spends nearly its whole range on the few largest cells, so everything else reads as one flat colour.
vmin has to be above zero. Log scales don’t have room for zero or negatives, so mask or clip them before plotting.
More Matplotlib axis guides on this site:
- Matplotlib set_xticklabels
- Matplotlib tick_params
- Rotate tick labels 45 and 90 degrees
- Remove tick labels and ticks
- Matplotlib tight_layout
Frequently asked questions
How do I show minor ticks on a Matplotlib log scale?
Set a LogLocator with subs: ax.xaxis.set_minor_locator(LogLocator(base=10.0, subs=np.arange(2, 10) * 0.1, numticks=100)). The parameters are described in the LogLocator reference.
Why does minorticks_on() do nothing on my log plot?
Over many decades LogLocator drops the in-between ticks to avoid clutter. Set the minor locator explicitly with subs instead.
What does subs=np.arange(2, 10) * 0.1 mean?
It is the list 0.2 to 0.9, the fractional positions inside each decade, so ticks land on 2, 3, 4 up to 9 of every power of ten.
Why do my minor ticks still not appear?
Usually numticks. Its default is small enough to suppress the ticks you asked for, so pass something generous such as 100.
What is the difference between loglog and set_xscale(‘log’)?
loglog plots and sets both scales in one call. set_xscale only changes the scale, which suits an axes you have already drawn on.
How do I put a colorbar on a log scale?
Pass norm=LogNorm(vmin=..., vmax=...) to the plotting call. The colorbar picks up the log mapping automatically.
Can a log scale show zero or negative values?
No. Use symlog for data crossing zero, or mask the non-positive values before plotting.
Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn